home *** CD-ROM | disk | FTP | other *** search
/ GameStar 2004 April / Gamestar_61_2004-04_dvdb.iso / DVDStar / Editace / hltp.exe / {app} / Applications / QuArK / plugins / mapmadsel.py < prev    next >
Encoding:
Python Source  |  2004-01-05  |  37.5 KB  |  1,194 lines

  1.  
  2. ########################################################
  3. #
  4. #                      Mad Selector Plugin
  5. #                        v2, August 2001
  6. #                     works with Quark 6.3  
  7. #
  8. #
  9. #        by tiglari@planetquake.com, with advice
  10. #          and code snippets from Armin Rigo, and
  11. #         bug-reports and suggestions from decker.
  12. #     
  13. #
  14. #   You may freely distribute modified & extended versions of
  15. #   this plugin as long as you give due credit to tiglari &
  16. #   Armin Rigo. (It's free software, just like Quark itself.)
  17. #
  18. #   Please notify bugs & improvements to tiglari@planequake.com
  19. #
  20. #   Extension of selection from polys eliminated because it's a mess,
  21. #
  22. #   Lots of commands for manipulating group structure added,
  23. #    also for restricting the selection.
  24. #
  25. #   Exports a facility for building popup menus giving operations
  26. #    on higher things in the tree (like Navigate Tree)
  27. #
  28. ###
  29. ##########################################################
  30.  
  31.  
  32. #$Header: /cvsroot/quark/runtime/plugins/mapmadsel.py,v 1.28 2003/11/27 08:17:22 cdunde Exp $
  33.  
  34.  
  35. Info = {
  36.    "plug-in":       "Mad Selector",
  37.    "desc":          "Manipulating selection in various ways",
  38.    "date":          "10 June 1999, rev Aug 2001",
  39.    "author":        "tiglari",
  40.    "author e-mail": "tiglari@planetquake.com",
  41.    "quark":         "Version 6.3" }
  42.  
  43.  
  44. import quarkx
  45. import quarkpy.mapmenus
  46. import quarkpy.mapentities
  47. import quarkpy.qmenu
  48. import quarkpy.mapeditor
  49. import quarkpy.mapcommands
  50. import quarkpy.mapoptions
  51. import quarkpy.maphandles
  52. import quarkpy.dlgclasses
  53. import maptagside
  54. import faceutils
  55. from quarkpy.maputils import *
  56.  
  57. import mapfacemenu
  58.  
  59.  
  60. types = {
  61.     ":d": "duplicator",
  62.     ":e": "point entity",
  63.     ":g": "group",
  64.     ":b": "brush entity",
  65.     ":p": "polyhedron",
  66.     ":f": "face"  }
  67.  
  68.  
  69. #
  70. #-----------  Right-menu tree-view manipulation --------
  71. #
  72.  
  73. #
  74. # for an object current, returns menu items for action
  75. #   upon that item.  Define this in a file importing from
  76. #   mapmadsel to make additional parent popup menus.
  77. #
  78. def navTreePopupItems(current, editor, restricted):
  79.     name = current.shortname
  80.     select = qmenu.item("&Select",SelectMe,"Select")
  81.     stash = qmenu.item("&Mark", StashMe, "Marking is a preliminary for the `Reorganize Tree' operations, which help to (re)organize the group-structure in the tree-view.\n\nFor example you can mark a group, and then later insert a selected entity into it, or mark an entity, and later insert it into or over (in the treeview) the selected group.\n\nReorganize Tree operations that can't be applied sensibly to the selected and marked objects are supposed to be greyed out; if they aren't it's a bug.")
  82.     restrict = qmenu.item("&Restrict", RestrictByMe, "|Restricts selections to being within this.\n\nGood if for example you want to work on the details of a desk or staircase for a while.")
  83.     zoom = qmenu.item("&Zoom", ZoomToMe, "Fill the views with selected.")
  84.     if restricted:
  85.       select.state=qmenu.disabled
  86.     if current.type == ":e" or current.type == ":f":
  87.       restrict.state = qmenu.disabled
  88.     item = qmenu.popup(name,[zoom,select,restrict,stash],None,"|This is the name of some group or brush entity that contains what you have selected.\n\nLook at its submenu for stuff you can do!\n\nIf there's a bar in the menu, then the `Restrict Selections' menu item is checked, and you can only select stuff above the bar.")
  89.     item.menuicon = current.geticon(1)
  90.     item.object = current
  91.     stash.object = select.object = restrict.object = zoom.object = current
  92. #   restrict.object = current
  93.     return item
  94.  
  95.  
  96. #
  97. # For an object o, uses the parentpopupitems function to
  98. #   construct a menu of popups, one for each object over o.
  99. #
  100. def buildParentPopupList(o, parentpopupitems, editor):
  101.     current=o
  102.     list = []
  103.     if editor is None:
  104.         editor = mapeditor()
  105.     restrictor = getrestrictor(editor)
  106.     restricted = 0
  107.   #  while current.name != "worldspawn:b":
  108.     while current != None:
  109.         list.append(parentpopupitems(current, editor, restricted))
  110.         if current==restrictor and menrestsel.state == qmenu.checked:
  111.             list.append(qmenu.sep)
  112.             restricted = 1
  113.         current = current.treeparent;
  114.   #  list.reverse()
  115.     return list    
  116.  
  117.  
  118. def buildParentPopup(o,parentpopup, parentpopupitems, editor=None):
  119.     list = buildParentPopupList(o,parentpopupitems, editor)
  120.     if list == []:
  121.         parentpopup.state=qmenu.disabled
  122.     else:
  123.         parentpopup.items = list
  124.     return parentpopup
  125.      
  126. #
  127. # This makes a parent popup item for navigating the tree
  128. #   above the selected item.  Imitate in other files to
  129. #   make more, with replacement for navTreePopupItems.
  130. #
  131. def navTreePopup(o,editor):
  132.     parentSelPop = qmenu.popup("&Navigate Tree", hint = "|The submenu that appears comprises the currently selected object at the top, and below it, the map objects (polys, groups & brush entities) that are above it in the group tree-structure.\n\nIf you put the cursor over one of these, you will get a further sub-menu with relevant commands to select from.")
  133.     return buildParentPopup(o,parentSelPop,navTreePopupItems,editor)
  134.  
  135.  
  136. def RestrictByMe(m):
  137.   editor = mapeditor()
  138.   if editor is None:
  139.     maptagside.squawk("no editor")
  140.   if m.object.name == "worldspawn:b":
  141.     del editor.restrictor
  142.     menrestsel.state = qmenu.disabled
  143.     editor.invalidateviews()
  144.     return
  145.   editor.restrictor = m.object
  146.   editor.invalidateviews()
  147.   menrestsel.state = qmenu.checked
  148.  
  149. def vec2rads(v):
  150.     "returns pitch, yaw, in radians"
  151.     v = v.normalized
  152.     import math
  153.     pitch = (-math.sin(v.z))
  154.     yaw = math.atan2(v.y, v.x)
  155.     return pitch, yaw
  156.  
  157. def ZoomToMe(m):
  158.     editor = mapeditor()
  159.     if editor is None:
  160.         maptagside.squawk("no editor")
  161.  
  162.     else:
  163.         zoomToMeFunc(editor,m.object)
  164.  
  165. quarkpy.mapoptions.items.append(quarkpy.mapoptions.toggleitem("Look and Zoom in 3D views", "3Dzoom", (1,1),
  166.       hint="|Look and Zoom in 3D views:\n\nIf this menu item is checked, it will zoom in and center on the selection(s) in all of the 3D views when the 'Zoom to selection' button on the 'Selection Toolbar' is clicked.\n\nIf a face is selected and the 'Shift' key is held down, it will look at the other side of the face and strive to center it in the view.\n\nIf this menu item is unchecked, it will only look in the selection(s) direction from the current camera position.|intro.mapeditor.menu.html#optionsmenu"))
  167.  
  168.         
  169. def zoomToMeFunc(editor,object):
  170.     #
  171.     #  regular views
  172.     #
  173.     layout = editor.layout
  174.     scale1, center1 = AutoZoom(layout.views, quarkx.boundingboxof([object]), scale1=layout.MAXAUTOZOOM)
  175.     if scale1 is not None:
  176.         layout.editor.setscaleandcenter(scale1, center1)
  177.     #
  178.     # 3d views with zoom feature
  179.     #
  180.     views = filter(lambda v:v.info["type"]=="3D", editor.layout.views)
  181.     for view in views:
  182.         pos, yaw, pitch = view.cameraposition
  183.         def between(pair):
  184.             return (pair[0]+pair[1])/2
  185.         def objsize(pair):
  186.             return (pair[1]-pair[0])
  187.         center = between(quarkx.boundingboxof([object]))
  188.         size = objsize(quarkx.boundingboxof([object]))
  189.         dir = (center-pos).normalized
  190.         pitch, yaw = vec2rads(dir)
  191.         if MapOption("3Dzoom"):
  192.             brush = editor.layout.explorer.uniquesel
  193.  
  194.             if quarkx.keydown('\020')==1: # shift is down
  195.                 reverse = 1
  196.             else:
  197.                 reverse = 0
  198.  
  199.             if object.type == ":f":
  200.                 face = editor.layout.explorer.uniquesel
  201.                 dist = abs(pos - face.origin)
  202.                 center = face.origin
  203.                 if size.x == 0:
  204.                     if size.y > size.z:
  205.                         dist = size.y
  206.                     else:
  207.                         dist = size.z
  208.                 else:
  209.                     if size.y == 0:
  210.                         if size.x > size.z:
  211.                             dist = size.x
  212.                         else:
  213.                             dist = size.z
  214.                     else:
  215.                         if size.z == 0:
  216.                             if size.x > size.y:
  217.                                 dist = size.x
  218.                             else:
  219.                                 dist = size.y
  220.                         else:
  221.                             if size.x > size.y:
  222.                                 if size.x > size.z:
  223.                                     dist = size.x*1.3
  224.                                 else:
  225.                                     dist = size.z*1.3
  226.                             else:
  227.                                 if size.y > size.z:
  228.                                     dist = size.y*1.3
  229.                                 else:
  230.                                     dist = size.z*1.3
  231.  
  232.                 if reverse:
  233.                     norm = -face.normal
  234.                 else:
  235.                     norm = face.normal
  236.                 pos = face.origin+dist*(norm)
  237.                 pitch, yaw = vec2rads(-norm)
  238.                 if size.z == 0:
  239.                     pitch = pitch*1.867
  240.                 view.cameraposition = pos, yaw, pitch
  241.                 editor.invalidateviews()
  242.  
  243.             else:
  244.                 if size.x > size.y:
  245.                     if size.x > size.z:
  246.                         if size.z > size.y:
  247.                             newposx = center.x - (size.x/2) - size.z
  248.                             pos = quarkx.vect(newposx,center.y,center.z)
  249.                         else:
  250.                             newposx = center.x - (size.x/2) - size.y
  251.                             pos = quarkx.vect(newposx,center.y,center.z)
  252.                     else:
  253.                         newposx = center.x - (size.x/2) - size.z
  254.                         pos = quarkx.vect(newposx,center.y,center.z)
  255.                 else:
  256.                     if size.y > size.z:
  257.                         newposx = center.x - (size.x/2) - size.y
  258.                         pos = quarkx.vect(newposx,center.y,center.z)
  259.                     else:  # This allows for entities without bounding boxes
  260.                         if size.x == 0:
  261.                             if size.y == 0:
  262.                                 if size.z == 0:
  263.                                     newposx = center.x - 100
  264.                                     pos = quarkx.vect(newposx,center.y,center.z)
  265.                                 else:continue
  266.                             else:continue
  267.                         else:
  268.                             newposx = center.x - (size.x/2) - size.z
  269.                             pos = quarkx.vect(newposx,center.y,center.z)
  270.                 yaw = 0
  271.                 pitch = 0
  272.         view.cameraposition = pos, yaw, pitch
  273.     editor.invalidateviews()
  274.  
  275.  
  276. def SelectMe(m):
  277.       import quarkpy.mapmenus
  278.       editor = mapeditor()
  279.       if editor is None:
  280.           maptagside.squawk("no editor")
  281.       else:
  282.           selectMeFunc(editor,m.object)
  283.           
  284. def selectMeFunc(editor, object):
  285.     #
  286.     # the tree-view
  287.     #
  288.     explorer = editor.layout.explorer
  289.     #
  290.     # is there a quicker way of getting the thing open in the tree view?
  291.     #
  292.     # is there an easier way to do this? line below wrecks buttons
  293.     #
  294. #    editor.layout.mpp.viewpage(btn)
  295.  
  296. # To stop error and give instructions.
  297.     if object.treeparent is None:
  298.         return
  299.  
  300.     Spec1 = qmenu.item("", quarkpy.mapmenus.set_mpp_page, "")
  301.     Spec1.page = 0
  302.     quarkpy.mapmenus.set_mpp_page(Spec1)
  303.  
  304.  
  305.  
  306.  
  307.     current = object.treeparent
  308.     if current is None:
  309.         return
  310.     olist = []
  311.     while current.name != "worldspawn:b":
  312.         olist[:0] = [current]
  313.         current = current.parent
  314.     for current in olist:
  315.         explorer.expand(current)
  316.     explorer.sellist=[object]
  317.     
  318. #
  319. # --------- stashing ---------
  320. # (like tagging, maybe should be an extension of tagging)
  321. #
  322.  
  323.  
  324.  
  325. def stashitem(o):
  326.   item = qmenu.item('Mark '+types[o.type], StashMe, "mark for tree-restructuring")
  327.   item.object = o
  328.   return item
  329.  
  330. def StashMe(m):
  331.   editor = mapeditor()
  332.   if editor is None: return
  333.   editor.marker = m.object
  334.   
  335. def getstashed(e):
  336.   try:
  337.     return e.marker
  338.   except (AttributeError) : return None
  339.   
  340. def clearstashed(e):
  341.     try:
  342.         del e.marker
  343.     except (AttributeError) : pass
  344.  
  345. #
  346. # -------------  restrictor -----------
  347. #   (like stash, for marking but just for restricting
  348. #    the selection)
  349. #
  350.  
  351. def getrestrictor(e):
  352.   try:
  353.     return e.restrictor
  354.   except (AttributeError) : return None
  355.  
  356.  
  357. #
  358. # ---------- insertinto ---------
  359. #
  360.  
  361. def insert_ok(insertee, goal):
  362.   if goal is None or insertee is None:
  363.     return 0
  364.   if insertee.type is ":f" and (goal.type is ":p") or (goal.type is ":g"):
  365.     return 1
  366.   if insertee.name=="worldspawn:b" or not (goal.type==":g" or goal.type==":b"):
  367.     return 0
  368.   return 1
  369.  
  370. def insertinto(o):
  371.   "inserts marked into selected"
  372.   editor=mapeditor()
  373.   marked = getstashed(editor)
  374.   if not insert_ok(marked, o):
  375.     item = qmenu.item("Insert marked into this",InsertIntoMe,"Mark something to insert it somewhere")
  376.     item.state=qmenu.disabled
  377.     return item
  378.   text = "Insert "+`marked.shortname`+" into this"
  379.   item = qmenu.item(text,InsertIntoMe,"Insert what you marked into this")
  380.   item.object = o
  381.   item.text = text
  382.   item.marked = marked
  383.   return item
  384.  
  385. def InsertIntoMe(m):
  386.    undo = quarkx.action()
  387.    undo.move(m.marked, m.object)
  388.    mapeditor().ok(undo, m.text)
  389.  
  390. def insertover(o):
  391.   "inserts marked over selected (o)"
  392.   editor=mapeditor()
  393.   marked = getstashed(editor)
  394.   if not insert_ok(marked, o.treeparent):
  395.     item = qmenu.item("Insert marked over this",InsertOverMe,"Mark something to insert it places")
  396.     item.state=qmenu.disabled
  397.     return item
  398.   text = "Insert "+`marked.shortname`+" over this"
  399.   item = qmenu.item(text,InsertOverMe,"|Insert what you marked over the position of this in the tree-view")
  400.   item.object = o
  401.   item.marked = marked
  402.   item.text = text
  403.   return item
  404.  
  405. def InsertOverMe(m):
  406.    undo = quarkx.action()
  407.    undo.move(m.marked, m.object.parent, m.object)
  408.    mapeditor().ok(undo, m.text)
  409.  
  410. def insertme(o):
  411.   "inserts this into marked"
  412.   editor=mapeditor()
  413.   marked = getstashed(editor)
  414.   if not insert_ok(o,marked):
  415.     item = qmenu.item("Insert this into marked",InsertMeInto,"Mark something to insert stuff into it")
  416.     item.state=qmenu.disabled
  417.     return item
  418.   text = "Insert this into "+`marked.shortname`
  419.   item = qmenu.item(text,InsertMeInto,"")
  420.   item.object = o
  421.   item.text = text
  422.   item.marked = marked
  423.   return item
  424.  
  425. def InsertMeInto(m):
  426.    undo = quarkx.action()
  427.    undo.move(m.object, m.marked)
  428.    mapeditor().ok(undo, m.text)
  429.  
  430.  
  431. def facelift(o):
  432.     "returns a menu item for lifting face into marked group"
  433.     editor = mapeditor()
  434.     marked = getstashed(editor)
  435.     help = "|Lifts face to marked group, removing coplanar faces within that group."
  436.     item = qmenu.item("&Lift to marked group",LiftMe,help)  
  437.     if marked is None:
  438.         item.state = qmenu.disabled
  439.         item.hint = item.hint+"\n\nFor this item to be enabled, there must be a marked group containing the face (Navigate tree|<some containing group>|Mark)."
  440.         return item
  441.     if not checktree(marked,o):
  442.         item.state = qmenu.disabled
  443.         item.hint = item.hint+"\n\nFor this item to be enabled, the marked group must contain the face"
  444.         return item
  445.     item.marked = marked
  446.     item.object=o
  447.     return item
  448.  
  449.  
  450. def LiftMe(m):
  451.    o=m.object
  452.    list = m.marked.findallsubitems("",":f")
  453.    undo = quarkx.action()
  454.    undo.move(m.object, m.marked)
  455.    for face in list:
  456.      if faceutils.coplanar(o, face) and o != face:
  457.        undo.exchange(face,None)
  458.    mapeditor().ok(undo, m.text)
  459.   
  460.  
  461. ###################################
  462. #
  463. # right-mouse menus for faces.  Messes up selected brush
  464. #
  465. ###################################
  466.  
  467. exttext = "|Extends the selection from this face to all the faces that make a single unbroken sheet with this one.\n\nSo you can for example move the bottom of a ceiling brush, and have the tops of the wall brushes follow, if they're on the same plane as the bottom of the ceiling.\n\nYou can also Link the selected faces, so that all of them can be snapped to the position of one of them with one click.|intro.mapeditor.menu.html#invertface"
  468.  
  469. def extendtolinked(editor,o):
  470.   "extend the selection to the linked faces"
  471.   item = qmenu.item("to &Linked faces",ExtendToLinkedClick,"Extend Selection to Linked Faces")
  472.   tag = o.getint("_tag")
  473.   if tag == 0:
  474.     item.state=qmenu.disabled
  475.   item.o = o
  476.   item.tag = tag
  477.   return item
  478.  
  479.  
  480. def ExtendToLinkedClick(m):
  481.   o = m.o
  482.   tag = m.tag
  483.   editor = mapeditor()
  484.   if editor is None:
  485.     return
  486.   allfaces = editor.Root.findallsubitems("",":f")
  487.   retfaces = [o]
  488.   for face in allfaces:
  489.     if face == o:
  490.       continue     
  491.     if face.getint("_tag")==tag:
  492.       retfaces.append(face)
  493.   if len(retfaces) > 1:
  494.     editor.layout.explorer.sellist = retfaces
  495.     editor.invalidateviews()
  496.   
  497.  
  498.  
  499. def extmenuitem(String, ClickFunction,o, helptext=""):
  500.   "make a menu-item with a side attached"
  501.   item = qmenu.item(String, ClickFunction, helptext)
  502.   item.obj = o
  503.   return item
  504.  
  505. def madfacemenu(o, editor, oldmenu=quarkpy.mapentities.FaceType.menu.im_func):
  506.   "the new right-mouse menu for faces"
  507.   menu = oldmenu(o, editor)
  508.   menu[:0] = [qmenu.popup("&Extend Selection", 
  509.                 [extendtolinked(editor,o),
  510.                  extmenuitem("to Adjacent faces",ExtendSelClick,o,exttext)]),
  511.               navTreePopup(o, editor),
  512.               facelift(o),
  513.               quarkpy.qmenu.sep]
  514.   return menu  
  515.  
  516. quarkpy.mapentities.FaceType.menu = madfacemenu
  517.  
  518.  
  519. #
  520. # --------------- right-mouse menus for polys ---------
  521.  
  522.  
  523. grptext = "|Extends the selection to all sides forming a connected sheet with any side of the brush or group.\n\nSo if you move one, they all follow."
  524.  
  525. def reorganizePopItems(o):
  526.     return [insertinto(o),
  527.             insertover(o),
  528.             insertme(o)]
  529.  
  530. def restructurepopup(o):
  531.     reorganizePop = qmenu.popup("&Reorganize Tree", hint="Reorganize structure of grouping tree")
  532.     reorganizePop.items = reorganizePopItems(o)
  533.     return reorganizePop
  534.  
  535. def madpolymenu(o, editor, oldmenu=quarkpy.mapentities.PolyhedronType.menu.im_func):
  536.     "the new right-mouse menu for polys"
  537.     menu = oldmenu(o, editor)
  538.     menu[:0] = [extmenuitem("Extend Selection",ExtendSelClick,o,grptext),
  539.                 #stashitem(o),
  540.                 navTreePopup(o, editor),
  541.                 restructurepopup(o),
  542.   #              menrestsel,
  543.                 qmenu.sep]
  544.     return menu  
  545.  
  546. quarkpy.mapentities.PolyhedronType.menu = madpolymenu
  547.  
  548.  
  549. #
  550. #  ----------right-mouse menus for groups -------------
  551. #
  552.  
  553.  
  554.     
  555. def madgroupmenu(o, editor, oldmenu=quarkpy.mapentities.GroupType.menu.im_func):
  556.   "the new right-mouse menu for groups"
  557.   menu = oldmenu(o, editor)
  558.   menu[:0] = [#extmenuitem("Extended Selection",ExtendSelClick,o,grptext),
  559.               #stashitem(o),
  560.               navTreePopup(o, editor),
  561.               restructurepopup(o),
  562. #              menrestsel,
  563.               qmenu.sep]
  564.   return menu  
  565.  
  566. quarkpy.mapentities.GroupType.menu = madgroupmenu
  567.  
  568. #
  569. #  ----------right-mouse menus for groups -------------
  570. #
  571.  
  572.  
  573.     
  574. def madbezmenu(o, editor, oldmenu=quarkpy.mapentities.BezierType.menu.im_func):
  575.   "the new right-mouse menu for groups"
  576.   menu = oldmenu(o, editor)
  577.   menu[:0] = [navTreePopup(o, editor),
  578.               restructurepopup(o),
  579.               qmenu.sep]
  580.   return menu  
  581.  
  582. quarkpy.mapentities.BezierType.menu = madbezmenu
  583.  
  584.  
  585. def madentmenu(o, editor, oldmenu=quarkpy.mapentities.EntityType.menu.im_func):
  586.   "point entity menu"
  587.   menu = oldmenu(o, editor)
  588.   menu[:0] = [#stashitem(o),
  589.               navTreePopup(o, editor),
  590.               restructurepopup(o),
  591. #              menrestsel,
  592.               qmenu.sep]
  593.   return menu
  594.  
  595. quarkpy.mapentities.EntityType.menu = madentmenu
  596.  
  597.  
  598. def madbrushentmenu(o, editor, oldmenu=quarkpy.mapentities.BrushEntityType.menu.im_func):
  599.   menu = oldmenu(o, editor)
  600.   menu[:0] = [#stashitem(o),
  601.               navTreePopup(o, editor),
  602.               restructurepopup(o),
  603.               qmenu.sep]
  604.   return menu
  605.  
  606. quarkpy.mapentities.BrushEntityType.menu = madbrushentmenu
  607.  
  608. #
  609. # and the background menu
  610. #
  611.  
  612. def backmenu(editor, view=None, origin=None, oldbackmenu = quarkpy.mapmenus.BackgroundMenu):
  613.   menu = oldbackmenu(editor, view, origin)
  614.   menunrestrictenable(editor)
  615.   menu[:0] = [menunrestrict]
  616.   return menu
  617.  
  618. quarkpy.mapmenus.BackgroundMenu = backmenu
  619.  
  620.  
  621. #################################
  622. #
  623. #  Hacking drag
  624. #
  625. #  In truth, I don't remember what this stuff does
  626. #    anymore!!!!
  627. #
  628. ##################################
  629.  
  630. olddrag = quarkpy.qhandles.CenterHandle.drag
  631.  
  632. def maddrag(self, v1, v2, flags, view):
  633.   (old, new) = olddrag(self, v1, v2, flags, view)
  634.   if (new is not None) and (len(new) == 1):
  635.     try:
  636.       editor=mapeditor()
  637.       madsel = editor.madsel
  638.       #
  639.       # complex shenannigans to track dragged selections
  640.       #  'twould be cool if someone told me a better way
  641.       #
  642.       if old[0] is madsel.orig:
  643.         madsel.neworig = new[0]
  644.         #
  645.         # assumes that new faces will have the same positions in
  646.         # the subitems lists as the corresponding old ones (???)
  647.         #
  648.         oldfaces = old[0].findallsubitems("",":f")
  649. #        quarkx.msgbox(`len(oldfaces)`,MT_INFORMATION,MB_OK)
  650.         newfaces = new[0].findallsubitems("",":f")
  651.         newpairs=[]
  652.         for pair in madsel.extra:
  653.           newface = newfaces[oldfaces.index(pair[0])]
  654.           delta = newface.origin-pair[0].origin
  655. #          quarkx.msgbox(`delta`,MT_INFORMATION, MB_OK)
  656.           newcofaces=[]
  657.           for face in pair[1]:
  658.             newcoface = face.copy()
  659.             newcoface.translate(delta)
  660.             new.append(newcoface)
  661.             old.append(face)
  662.             newcofaces.append(newcoface)
  663.           newpair = (newface, newcofaces)
  664.           newpairs.append(newpair)
  665.         madsel.newextra = newpairs  
  666.     except (AttributeError): pass
  667.   return (old, new)
  668.  
  669. quarkpy.qhandles.CenterHandle.drag = maddrag
  670.  
  671.  
  672. ####################################
  673. #
  674. # hacking finishdrawing
  675. #
  676. #####################################
  677.  
  678. def getmadsel(obj):
  679.   "safe fetching of a mad selection"
  680.   try:
  681.      return obj.madsel
  682.   except (AttributeError): return None
  683.  
  684.  
  685. def madfinishdrawing(self, view): 
  686.   "the new finishdrawning routine"
  687.   Madsel.oldfinishdrawing(self, view)
  688.   editor = mapeditor()
  689.   madsel = getmadsel(editor)
  690.   if madsel is None: return
  691.   #
  692.   # clear all if original selection no longer in map
  693.   #   
  694. #  if not tigutes.checktree(editor.Root,editor.madsel.orig):
  695.   if editor.layout.explorer.sellist == [editor.madsel.neworig]:
  696.     editor.madsel.orig = editor.madsel.neworig
  697.     editor.madsel.extra = editor.madsel.newextra
  698.   if not editor.layout.explorer.sellist == [editor.madsel.orig]: 
  699.     editor.madsel = None
  700.     return
  701.   cv = view.canvas()
  702.   cv.pencolor = MapColor("Tag")
  703.   for pair in madsel.extra:
  704. #    quarkx.msgbox(`len(pair[1])`,MT_INFORMATION,MB_OK)
  705.     for face in pair[1]:
  706.       for vtx in face.vertices: # is a list of lists
  707.         p2 = view.proj(vtx[-1])  # the last one
  708.         for v in vtx:
  709.           p1 = p2
  710.           p2 = view.proj(v)
  711.           cv.line(p1,p2)
  712.  
  713.  
  714. class Madsel:
  715.   def __init__(self, orig):
  716.     self.orig = orig
  717.     self.neworig = None
  718.     self.extra = []
  719.   oldfinishdrawing = None    
  720.  
  721. def swapfinishdrawing(editor):
  722.   if Madsel.oldfinishdrawing is None:
  723.     Madsel.oldfinishdrawing = quarkpy.mapeditor.MapEditor.finishdrawing
  724.     quarkpy.mapeditor.MapEditor.finishdrawing = madfinishdrawing
  725.  
  726. #####################################3
  727. #
  728. # and finally the point of it all ...
  729. #
  730. #######################################
  731.  
  732.  
  733.  
  734. def ExtendSelClick(m):
  735.   "extends the selection to adjacent sides"
  736.   editor = mapeditor()
  737.   if editor is None: return
  738.   selection = editor.layout.explorer.sellist
  739.   if len(selection) == 1:    
  740.     sel = selection[0]
  741.     try:
  742.       item = m.obj
  743.       if item.type == ":f":
  744.         sel = item
  745.     except (AttributeError) : pass
  746.     if sel.type == ":f":
  747.       list = [sel];
  748.       lotsa = editor.Root.findallsubitems("",":f")
  749.       quarkx.extendcoplanar(list,editor.Root.subitems)
  750.       editor.layout.explorer.sellist = list
  751.     else:
  752.       swapfinishdrawing(editor)
  753.       editor.madsel = Madsel(sel)
  754.       faces = sel.findallsubitems("",":f")
  755.       for face in faces:
  756.         list = [face]
  757.         quarkx.extendcoplanar(list,editor.Root.subitems)
  758.         list.remove(face)
  759.         if len(list)>0:
  760.           editor.madsel.extra.append((face, list))
  761.       editor.invalidateviews()
  762.   else:
  763.     quarkx.msgbox("No multiple selections",MT_INFORMATION,MB_OK)
  764.    
  765.  
  766. def madclick(editor, view, x, y, oldclick=quarkpy.maphandles.ClickOnView):
  767.   if mennosel.state == qmenu.checked:
  768.     return []
  769.   if menrestsel.state == qmenu.checked:
  770.     restrictor = getrestrictor(editor)
  771.     if restrictor is not None:
  772.       return view.clicktarget(restrictor, x, y)
  773.   return oldclick(editor, view, x, y)
  774.  
  775. quarkpy.maphandles.ClickOnView = madclick  
  776.  
  777. def maddrawview(view, mapobj, mode, olddraw=quarkpy.qbaseeditor.drawview):
  778.   if menrestsel.state == qmenu.checked:
  779.     editor = mapeditor()
  780.     restrictor=getrestrictor(editor)
  781. #    if view.info["type"]=="3D":
  782.     if view.viewmode != "wire":
  783.        olddraw(view, restrictor, mode)
  784.     else:
  785.       view.drawmap(mapobj, mode, 0, restrictor)
  786.   else:
  787.     olddraw(view,mapobj,mode)
  788.     
  789. quarkpy.qbaseeditor.drawview = maddrawview
  790.  
  791. def RestSelClick(m):
  792.   editor=mapeditor()
  793.   if editor==None: return
  794.   editor.restrictor = editor.layout.explorer.uniquesel
  795.   menrestsel.state = qmenu.checked
  796.   editor.invalidateviews()
  797.     
  798. def NoSelClick(m):
  799.   editor=mapeditor()
  800.   if editor==None: return
  801.   if mennosel.state == qmenu.checked:
  802.     mennosel.state = qmenu.normal
  803.   else:
  804.     mennosel.state = qmenu.checked
  805.      
  806.  
  807. def Unrestrict(editor):
  808.   del editor.restrictor
  809.   menunrestsel.state = qmenu.disabled
  810.   editor.invalidateviews()
  811.  
  812.  
  813. def UnrestrictClick(m):
  814.     editor = mapeditor()
  815.     if editor is None: return
  816.   #    maptagside.squawk("no editor")
  817.     del editor.restrictor
  818.     menrestsel.state = qmenu.normal
  819.     editor.invalidateviews()
  820.     return
  821.  
  822. def ClearMarkClick(m):
  823.     editor = mapeditor()
  824.     if editor is None: return
  825.     clearstashed(editor)
  826.     
  827. ###############################
  828. #
  829. # browsing multiple selections
  830. #
  831. ###############################
  832.  
  833.  
  834. #
  835. # a dialog for choosing one of a list of selected items
  836. #
  837. class BrowseListDlg(quarkpy.dlgclasses.LiveBrowserDlg):
  838.  
  839.     size = (220,140)
  840.  
  841.     dlgdef = """
  842.         {
  843.         Style = "9"
  844.         Caption = "Browse List Dialog"
  845.  
  846.         collected: = {
  847.           Typ = "C"
  848.           Txt = "Selected:"
  849.           Items = "%s"
  850.           Values = "%s"
  851.           Hint = "These are the listed items.  Pick one," $0D " then push buttons on row below for action."
  852.         }
  853.  
  854.        
  855.         sep: = { Typ="S" Txt=""}
  856.  
  857.         buttons: = {
  858.           Typ = "PM"
  859.           Num = "3"
  860.           Macro = "browselist"
  861.           Caps = "OZB"
  862.           Txt = "Actions:"
  863.           Hint1 = "Open the tree-view to the chosen one"
  864.           Hint2 = "Zoom to the chosen one"
  865.           Hint3 = "Both open and Zoom to the chosen one"
  866.           
  867.         }
  868.  
  869.         num: = {
  870.           Typ = "EF1"
  871.           Txt = "# listed"
  872.         }
  873.  
  874.         sep: = { Typ="S" Txt=""}
  875.  
  876.         exit:py = {Txt="" }
  877.     }
  878.     """
  879.  
  880. #    def select(self, editor):
  881. #        editor.layout.explorer.sellist=[chosen]
  882. #        editor.invalidateviews()
  883.  
  884.     def open(self, editor):
  885.         selectMeFunc(editor,self.chosen)
  886.  
  887.     def zoom(self, editor):
  888.         zoomToMeFunc(editor,self.chosen)
  889.  
  890.     def both(self, editor):
  891.         if self is None:
  892.             quarkx.msgbox("No selection has been made\n\nYou must first select\nan item in the list\nfor this function to work.", MT_ERROR, MB_OK)
  893.         zoomToMeFunc(editor,self.chosen)
  894.         selectMeFunc(editor,self.chosen)
  895.  
  896. def macro_browselist(self, index=0):
  897.     editor = mapeditor()
  898.  
  899.     if len(editor.layout.explorer.sellist)>1:
  900. # cdunde-To give warning and instructions.
  901.         quarkx.msgbox("The whole group is selected.\n\nYou must first select\na spacific item in the list\nfor this function to work.", MT_ERROR, MB_OK)
  902.     else:
  903.         if editor is None: return
  904.         elif index==1:
  905.             editor.dlg_browselist.open(editor)
  906.         elif index==2:
  907.             editor.dlg_browselist.zoom(editor)
  908.         elif index==3:
  909.             editor.dlg_browselist.open(editor)
  910.             editor.dlg_browselist.zoom(editor)
  911.         
  912.         
  913. quarkpy.qmacro.MACRO_browselist = macro_browselist
  914.  
  915.  
  916. #
  917. # Creates a ListerDlg-descendent to browse a list.
  918. #
  919. def browseListFunc(editor, list):
  920.  
  921.     class pack:
  922.         "stick stuff here"
  923.     pack.collected=list
  924.  
  925.     def action(self,editor=editor):
  926.         editor.layout.explorer.sellist=[self.chosen]
  927.         editor.invalidateviews()
  928.     
  929.     BrowseListDlg('browselist', editor, pack, None, action)
  930.  
  931. browseHelpString="|Browse Multiple Selection:\n\nMakes a dialog for browsing the selected elements.|intro.mapeditor.menu.html#invertface"
  932.  
  933.  
  934. def linredmenu(self, editor, view):
  935.  
  936.     def browseSelClick(m, editor=editor):
  937.         browseListFunc(editor, editor.layout.explorer.sellist)
  938.  
  939.  
  940.     item = qmenu.item('&Browse Selection',browseSelClick,browseHelpString)
  941.     return [item]
  942.     
  943. quarkpy.qhandles.LinRedHandle.menu=linredmenu    
  944.     
  945. def multreemenu(sellist, editor, oldmenu=quarkpy.mapmenus.MultiSelMenu):
  946.  
  947.     def browseSelClick(m,editor=editor,sellist=sellist):
  948.         browseListFunc(editor,sellist)
  949.  
  950.         
  951.     item = qmenu.item('&Browse Selection', browseSelClick,browseHelpString)
  952.  
  953.     return [item]+oldmenu(sellist, editor)
  954.     
  955. quarkpy.mapmenus.MultiSelMenu = multreemenu
  956.  
  957. def browseMulClick(m):
  958.     editor=mapeditor()
  959.     if editor is None: return
  960.     browseListFunc(editor, editor.layout.explorer.sellist)
  961.  
  962. ########################################
  963. #
  964. # selection menu items
  965. #
  966. ########################################
  967.  
  968.  
  969. def invertFaceSelClick(m):
  970.     editor=mapeditor()
  971.     if editor is None: return
  972.     faces = filter(lambda x:x.type==':f', editor.layout.explorer.sellist)
  973.     polys = []
  974. #    debug('filtered')
  975.     for face in faces:
  976.         for poly in face.faceof:
  977.             if not poly in polys:
  978.                 polys.append(poly)
  979. #    debug('polys')
  980.     newfaces=[]
  981.     for poly in polys:
  982.         for face in poly.faces:
  983.             if not (face in newfaces or face in faces):
  984.                 newfaces.append(face)
  985. #    debug('faces')
  986.     editor.layout.explorer.sellist=newfaces
  987.     editor.invalidateviews()
  988.     
  989.  
  990. meninvertfacesel = quarkpy.qmenu.item("&Invert Face Selection", invertFaceSelClick, "|Invert Face Selection:\n\nThis is for polys containing faces that are currently selected, deselect these and select the other, currently unselected, faces.|intro.mapeditor.menu.html#invertface")
  991.  
  992. menrestsel = quarkpy.qmenu.item("&Restrict to Selection", RestSelClick,"|Restrict to Selection:\n\nRestrict selections to within the current restrictor group, if any, which you can set with by clicking `Containing Groups I Some Item I Restrict' on the right mouse menu for polys, etc.|intro.mapeditor.menu.html#invertface")
  993.  
  994. menextsel = quarkpy.qmenu.item("&Extend Selection from Face", ExtendSelClick, exttext)
  995.  
  996. mennosel = quarkpy.qmenu.item("No Selection in Map Views", NoSelClick, "|No Selection in Map Views:\n\nWhen this menu item is checked, selection in the map views is prevented.\n\nThis is useful when touring with the 3d viewer, to prevent selecting things accidentally.|intro.mapeditor.menu.html#optionsmenu");
  997.  
  998. menunrestrict = quarkpy.qmenu.item("&Unrestrict Selection",UnrestrictClick,"|Unrestrict Selection:\n\nWhen selection is restricted (see the Containing Groups right-mouse menu), clicking on this will unrestrict the selection & restore things to normal.|intro.mapeditor.menu.html#invertface")
  999.  
  1000. browseItem = qmenu.item("Browse Multiple Selection",browseMulClick,browseHelpString)
  1001.  
  1002. zoomItem = qmenu.item("&Zoom to selection", ZoomToMe, "|Zoom to selection:\n\nZooms the map views in to the selection.|intro.mapeditor.menu.html#invertface")
  1003.  
  1004. def menunrestrictenable(editor):
  1005.   if getrestrictor(editor) is None:
  1006.     menunrestrict.state=qmenu.disabled
  1007.   else:
  1008.     menunrestrict.state=qmenu.normal
  1009.  
  1010. for menitem, keytag in [(menextsel, "Extend Selection"),
  1011.                         (menunrestrict, "Unrestrict Selection"),
  1012.                         (menrestsel, "Restrict to Selection"),
  1013.                         (browseItem, "Browse Multiple Selection"),
  1014.                         (meninvertfacesel, "Invert Face Selection"),
  1015.                         (zoomItem, "Zoom to Selection")]:
  1016.  
  1017.     MapHotKey(keytag,menitem,quarkpy.mapselection)
  1018.  
  1019.  
  1020. #
  1021. # -- selection menu items
  1022. #
  1023.  
  1024. stashItem = qmenu.item("&Mark selection", StashMe, "|Mark selection:\n\nThis command designates the selection as a special element for other (mostly somewhat advanced) commands, such as 'Lift face to marked group' on the face RMB, or the 'Reorganize Tree' commands on various map object RMB's.|intro.mapeditor.menu.html#invertface")
  1025.  
  1026. clearItem = qmenu.item("Clear Mark", ClearMarkClick, "|Clear Mark:\n\nThis cancels the Mark selection.|intro.mapeditor.menu.html#invertface")
  1027.  
  1028. def selectionclick(menu, oldcommand=quarkpy.mapselection.onclick):
  1029. #    reorganizePop.state = parentSelPop.state=qmenu.disabled
  1030.     menrestsel.state=menextsel.state=qmenu.disabled
  1031.     meninvertfacesel.state = stashItem.state = zoomItem.state = qmenu.disabled
  1032.     oldcommand(menu)
  1033.     editor = mapeditor()
  1034.     if editor is None: return
  1035.     menunrestrictenable(editor)
  1036.     sellist = editor.layout.explorer.sellist
  1037.     if filter(lambda x:x.type==':f', sellist):
  1038.         meninvertfacesel.state=qmenu.normal
  1039.     if len(sellist)>1:
  1040. #cdunde-to keep Cancel Selection active
  1041.         browseItem.state=quarkpy.mapselection.removeItem.state=qmenu.normal
  1042.     else:
  1043.         browseItem.state=qmenu.disabled
  1044.     if len(sellist)==1:
  1045.         menrestsel.state=qmenu.normal
  1046. #cdunde-to toggel each other
  1047.     if menunrestrict.state==qmenu.normal:
  1048.         menrestsel.state=qmenu.disabled
  1049.     sel = editor.layout.explorer.uniquesel
  1050.     marked = getstashed(editor)
  1051.     if marked is None:
  1052.         clearItem.state=qmenu.disabled
  1053.     else:
  1054.         clearItem.state=qmenu.normal
  1055.  
  1056.     if sel is None: return
  1057.  
  1058. #
  1059. #  this stuff isn't working right; canned
  1060. #
  1061. #    for popup, items in ((parentSelPop, buildParentPopupList(sel,navTreePopupItems),editor),
  1062. #                         (reorganizePop, reorganizePopItems(sel))):
  1063. #        popup.items = items
  1064. #        popup.state=qmenu.normal
  1065.     stashItem.object = zoomItem.object = sel
  1066.     stashItem.state = zoomItem.state = qmenu.normal
  1067. #    debug('greetings mortals')
  1068. #    if menrestsel.state != qmenu.checked:
  1069. #        menrestsel.state=qmenu.normal
  1070.     if sel.type == ':f':
  1071.         menextsel.state = quarkpy.qmenu.normal
  1072.  
  1073.  
  1074. quarkpy.mapselection.onclick = selectionclick  
  1075.  
  1076. quarkpy.mapselection.items.append(qmenu.sep)
  1077. #
  1078. # Canned cuz not working
  1079. #
  1080. #quarkpy.mapselection.items.append(parentSelPop)
  1081. #quarkpy.mapselection.items.append(reorganizePop)
  1082. quarkpy.mapselection.items.append(meninvertfacesel)
  1083. quarkpy.mapselection.items.append(menextsel)
  1084. quarkpy.mapselection.items.append(browseItem)
  1085. quarkpy.mapselection.items.append(menunrestrict)
  1086. quarkpy.mapselection.items.append(menrestsel)
  1087. quarkpy.mapselection.items.append(zoomItem)
  1088. quarkpy.mapselection.items.append(stashItem)
  1089. quarkpy.mapselection.items.append(clearItem)
  1090.  
  1091. #
  1092. #  -- options menu items
  1093. #
  1094. quarkpy.mapoptions.items.append(qmenu.sep)
  1095. quarkpy.mapoptions.items.append(mennosel)
  1096.  
  1097. ## Jan 28, 1999 - rewrote extension code to use quarkx.extendcoplanar
  1098. #     added flyover help.
  1099. # ----------- REVISION HISTORY ------------
  1100. #
  1101. #
  1102. # $Log: mapmadsel.py,v $
  1103. # Revision 1.28  2003/11/27 08:17:22  cdunde
  1104. # To update 3D Zoom to selection feature for faces
  1105. #
  1106. # Revision 1.27  2003/11/22 08:13:57  cdunde
  1107. # To update plugin and infobase for final version of 3D views Zoom feature
  1108. #
  1109. # Revision 1.26  2003/11/15 09:30:55  cdunde
  1110. # To add 3D zoom feature to Selection toolbar and Options menu with Infobase update
  1111. #
  1112. # Revision 1.25  2003/04/07 14:01:13  cdunde
  1113. # Multiple selections-fix error, add warning with instructions and keep Cancel Selections active.
  1114. #
  1115. # Revision 1.24  2003/03/28 02:54:40  cdunde
  1116. # To update info and add infobase links.
  1117. #
  1118. # Revision 1.23  2003/03/25 09:41:46  tiglari
  1119. # fix enablement bugs
  1120. #
  1121. # Revision 1.22  2003/03/25 08:29:36  cdunde
  1122. # To update info and make infobase links
  1123. #
  1124. # Revision 1.21  2003/03/17 01:48:49  cdunde
  1125. # Update hints and add infobase links where needed
  1126. #
  1127. # Revision 1.20  2002/05/19 04:42:55  tiglari
  1128. # Hot key for Zoom to Selection
  1129. #
  1130. # Revision 1.19  2002/05/18 22:38:31  tiglari
  1131. # remove debug statement
  1132. #
  1133. # Revision 1.18  2002/03/30 02:49:50  tiglari
  1134. # fixed bug whereby selection menu wasn't enabling/disabling properly
  1135. #
  1136. # Revision 1.17  2001/08/28 22:50:24  tiglari
  1137. # 'Invert face selection command' added
  1138. #
  1139. # Revision 1.16  2001/08/05 08:03:07  tiglari
  1140. # ListerDlg->LiveBrowserDlg
  1141. #
  1142. # Revision 1.15  2001/08/03 11:50:15  tiglari
  1143. # facilities for putting tree nav item on selection menu was causing free-ing
  1144. #  errors, so disabled it.  Reorganized & renamed a bit to provide an
  1145. #  explortable parent-menu facility
  1146. #
  1147. # Revision 1.14  2001/08/02 02:51:28  tiglari
  1148. # browse multiselection list
  1149. #
  1150. # Revision 1.13  2001/07/24 01:09:49  tiglari
  1151. # clear mark command
  1152. #
  1153. # Revision 1.12  2001/05/21 11:57:19  tiglari
  1154. # remove some debugs
  1155. #
  1156. # Revision 1.11  2001/05/06 08:03:17  tiglari
  1157. # put Mark, Zoom on selection menu
  1158. #
  1159. # Revision 1.10  2001/05/04 06:43:51  tiglari
  1160. # Accelerators added to selection menu
  1161. #
  1162. # Revision 1.9  2001/05/04 03:51:07  tiglari
  1163. # shift commands from commands to (new) selection menu
  1164. #
  1165. # Revision 1.8  2001/04/29 04:09:43  tiglari
  1166. # reorganize/navigate tree added to selection menu, some internal reorgs.
  1167. #
  1168. # Revision 1.7  2001/04/27 04:23:06  tiglari
  1169. # add tree nav/reorg to bezier menu; remove extend selection from group
  1170. #
  1171. # Revision 1.6  2001/04/15 06:07:12  tiglari
  1172. # use new coplanar function from faceutils, enhance hint text for
  1173. # lift face to marked group (hints explain why item is disabled)
  1174. #
  1175. # Revision 1.5  2001/03/29 21:04:59  tiglari
  1176. # split UnrestrictClick into interface & exective functions
  1177. #
  1178. # Revision 1.4  2001/03/20 08:02:16  tiglari
  1179. # customizable hot key support
  1180. #
  1181. # Revision 1.3.2.1  2001/03/11 22:08:15  tiglari
  1182. # customizable hot keys
  1183. #
  1184. # Revision 1.3  2001/03/06 09:52:27  tiglari
  1185. # ZoomTo aims 3d view cameras (no pos change yet, so not true zooming
  1186. #  in 3d views)
  1187. #
  1188. # Revision 1.2  2000/06/03 10:25:30  alexander
  1189. # added cvs headers
  1190. #
  1191. #
  1192. #
  1193. #
  1194.